#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <signal.h>
#include <errno.h>
#define PORT 9966
/*epoll 最大事件数*/
#define MAX_EVENTS  10
#define RECVBUF_LEN 1024
const char* serverip = "0";
/*
epoll流程:
    epoll_create1();创建句柄
    epoll_ctl();添加事件/文件描述符
    epoll_wait();检测事件是否发生
*/
/*
typedef union epoll_data
{
  void *ptr;//自定义数据数据结构,该数据结构应包含fd,不能与ptr同时存在
  int fd; //文件描述符,不能和ptr同时存在
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	// Epoll events
  epoll_data_t data;	//User data variable
} __EPOLL_PACKED;*/
int tcp_init()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    /**
     * @description:创建一个套接字
     * @param {*} 第一个参数：协议族，第二个参数：协议类型，第三个参数：协议编号
     * @return {*}成功返回套接字文件描述符，失败返回-1；
     */
    if(-1 == sockfd) {
        perror("socket:\n");
        return -1;
    }

    int opt = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt error");
    }
    // inter协议族

    struct sockaddr_in myaddr;
    //客户端连接的地址信息
    // struct sockaddr_in clientaddr;

    //协议族
    myaddr.sin_family = AF_INET;
    //端口号,用htons函数将它转换为网络字节序
    myaddr.sin_port = htons(PORT);
    // ip地址,0表示本机,inet_addr,将ip转换为网络字节序
    myaddr.sin_addr.s_addr = inet_addr(serverip);
    /**/
    // inet_pton() 字符串转网络字节
    // inet_ntop() 网络字节转字符串
    // bzero(&(myaddr.sin_zero), 8);

    if(-1 == bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr)))
    /**
     * @description:绑定套接字，端口号
     * @param
     * {*}第一个参数：套接字，第二个参数：指向sockaddr的指针，第三个参数：sockaddr结构体大小
     * @return {*}0表示绑定成功，-1表示绑定失败
     */
    {
        perror("bind:\n");
        return -1;
    }
    printf("bind success\n");
    if(-1 == listen(sockfd, 5))
    /**
     * @description:监听端口，初始化可连接队列，套接字变为监听套接字
     * @param {*}第一次参数：套接字；第二个参数：等待队列长度
     * @return {*}0表示成功，-1表示失败
     */
    {
        perror("listen:");
        return -1;
    }
    printf("listen success\n");
    return sockfd;
}
int setRecvTimeout(int sockfd)
{
    struct timeval ev;
    ev.tv_sec  = 0;
    ev.tv_usec = 300 * 1000;
    return setsockopt(
        sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&ev, sizeof(ev));
}
void sign_handle(int sign)
{
    /*do nothing*/
}
int main()
{
    int                epoll_fd; /*epoll句柄*/
    int                listenfd;
    int                acceptfd;
    int                nfds;
    struct epoll_event ev; /*用于注册(epoll_ctl中的add)epoll事件*/
    struct epoll_event events[MAX_EVENTS]; /*存储epoll_wait触发的事件*/
    int                datalen              = 0; /*收到的数据长度*/
    char               recvbuf[RECVBUF_LEN] = {0};
    int                ret                  = 0;
    int                i                    = 0;
    struct sockaddr_in clientaddr;
    int                clientaddrlen = sizeof(clientaddr);
    signal(SIGPIPE, sign_handle);
    listenfd = tcp_init();
    if(-1 == listenfd) { return -1; }
    epoll_fd = epoll_create1(0);
    if(-1 == epoll_fd) {
        perror("epoll_create:\n");
        return -1;
    }

    /*设置监听读事件和oneshot(使1个socket连接只能被一个线程处理)*/
    ev.events = EPOLLIN; //设置监听读事件
    /*添加文件描述符*/
    ev.data.fd = listenfd;
    /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl().
#define EPOLL_CTL_ADD 1	/* 添加fd.
#define EPOLL_CTL_DEL 2	/* 删除fd. epoll_ctl最后一个参数置为NULL;
#define EPOLL_CTL_MOD 3	/* 修改已经注册的事件.操作方法与add类似  */
    //水平触发:socket对应的缓冲区有数据,就会被wait返回
    //边沿触发:socket有读或写事件发生,才会被wait返回,读取对应缓冲区的数据.
    ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenfd, &ev);
    if(-1 == ret) {
        perror("epoll_ctl\n");
        return -1;
    }
    int timeout = 5 * 1000;
    for(;;) {
        /*等待事件发生,返回触发事件的数量*/
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout);
        for(i = 0; i < nfds; ++i) {
            if(events[i].data.fd == listenfd) {
                /*accept后两个参数用于保存对端信息,如果没有获取对端信息的需求可以直接为NULL*/
                acceptfd = accept(events[i].data.fd,
                    (struct sockaddr*)&clientaddr, &clientaddrlen);
                if(-1 == acceptfd) {
                    perror("accept:\n");
                    continue;
                }
                if(-1 == setRecvTimeout(acceptfd)) {
                    printf("set recv time failed\n");
                }
                /*将新建立连接的socket添加近epoll*/
                /*注册事件为读事件*/
                ev.events  = EPOLLIN;
                ev.data.fd = acceptfd;
                ret        = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, acceptfd, &ev);
                if(-1 == ret) {
                    perror("epoll_ctl\n");
                    // close(acceptfd);/*并非完全关闭,如果其它进程共享该套接字,仍然能正常读写*/
                    /*切断进程共享的套接字连接*/
                    shutdown(acceptfd, SHUT_RDWR);
                    close(acceptfd);
                    continue;
                    ;
                } else {
                    printf("session fd = %d\n", acceptfd);
                }
            } else {
                bzero(recvbuf, RECVBUF_LEN);
                datalen = recv(events[i].data.fd, recvbuf, RECVBUF_LEN, 0);
                if(0 >= datalen) {
                    if(errno == EAGAIN || errno == EWOULDBLOCK) {
                        printf("no data,no error\n");
                        continue;
                    }
                    printf("session %d close\n", events[i].data.fd);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                    shutdown(
                        events[i].data.fd, SHUT_RDWR); //关闭连接,套接字还在
                    close(
                        events[i]
                            .data
                            .fd); //关闭套接字(如果连接存在,会先关闭关闭连接),删除fd对应的信息
                    continue;
                }
                printf("client data :%s\n", recvbuf);
                strncpy(recvbuf, "hello world", 12);
                send(events[i].data.fd, recvbuf, 12, 0);
            }
        }
    }
    return 0;
}